package com.study.crypto.basic.utils;

import com.study.crypto.basic.signer.Signer;
import com.study.crypto.basic.signer.SignerFactory;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;

import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.Locale;

/**
 * @author Songjin
 * @since 2020-12-30 18:52
 */
public final class CertUtils_ {
    
    /**
     * 产生证书请求
     *
     * @param subject    证书使用主体
     * @param publicKey  公钥
     * @param privateKey 私钥
     * @return pkcs10
     */
    public static PKCS10CertificationRequest generateCSR(X500Name subject, PublicKey publicKey, PrivateKey privateKey) throws IOException, CryptoException, GeneralSecurityException, CMSException {
        SubjectPublicKeyInfo     publicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
        CertificationRequestInfo info          = new CertificationRequestInfo(subject, publicKeyInfo, new DERSet());
        AlgorithmIdentifier      signAlgo      = getSignAlgo(publicKeyInfo.getAlgorithm());
        Signer                   signer        = SignerFactory.produce(publicKeyInfo.getAlgorithm().getAlgorithm());
        byte[]                   signature     = signer.sign(info.getEncoded(ASN1Encoding.DER), privateKey);
        return new PKCS10CertificationRequest(new CertificationRequest(info, signAlgo, new DERBitString(signature)));
    }

    /**
     * 证书请求验证
     * @param csr
     * @return
     * @throws Exception
     */
    public static boolean verifyCSR(PKCS10CertificationRequest csr) throws Exception {
        byte[]    signature = csr.getSignature();
        Signer    signer    = SignerFactory.produce(csr.getSignatureAlgorithm().getAlgorithm());
        byte[]    encoded   = csr.toASN1Structure().getCertificationRequestInfo().getEncoded(ASN1Encoding.DER);
        PublicKey publicKey = KeyUtils.obtainPublicKey(csr.getSubjectPublicKeyInfo());
        return signer.verify(encoded, signature, publicKey);
    }

    /**
     * 签发终端用户证书
     * @param csr              证书请求p10
     * @param issuerPrivateKey 颁发者私钥
     * @param issuerCert       颁发者证书
     * @param notBefore        起始时间
     * @param notAfter         截止时间
     * @return
     * @throws Exception
     */
    public static Certificate generateUserCertificate(PKCS10CertificationRequest csr, PrivateKey issuerPrivateKey, byte[] issuerCert, Date notBefore, Date notAfter, BigInteger serialNumber) throws Exception {
        if (!verifyCSR(csr)) {
            throw new IllegalArgumentException("证书请求验证失败");
        }
        X509CertificateHolder issuer   = new X509CertificateHolder(issuerCert);
        X500Name              subject  = csr.getSubject();
        BcX509ExtensionUtils  extUtils = new BcX509ExtensionUtils();
        ExtensionsGenerator   extGen   = new ExtensionsGenerator();
        // entity cert
        extGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(false));
        extGen.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature));
        // 授权密钥标识
        extGen.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(issuer));
        // 使用者密钥标识
        extGen.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(csr.getSubjectPublicKeyInfo()));
        V3TBSCertificateGenerator tbsGen = new V3TBSCertificateGenerator();
        tbsGen.setSerialNumber(new ASN1Integer(serialNumber));
        tbsGen.setIssuer(issuer.getSubject());
        tbsGen.setStartDate(new Time(notBefore, Locale.CHINA));
        tbsGen.setEndDate(new Time(notAfter, Locale.CHINA));
        tbsGen.setSubject(subject);
        tbsGen.setSubjectPublicKeyInfo(csr.getSubjectPublicKeyInfo());
        tbsGen.setExtensions(extGen.generate());
        // 签名算法标识等于颁发者证书的密钥算法标识
        tbsGen.setSignature(issuer.getSubjectPublicKeyInfo().getAlgorithm());
        TBSCertificate tbs = tbsGen.generateTBSCertificate();
        return assembleCert(tbs, issuer.getSubjectPublicKeyInfo(), issuerPrivateKey);
    }
    
    /**
     * 生成自签名根证书
     * @param subject   证书主体
     * @param keyPair   密钥对
     * @param notBefore 起始时间
     * @param notAfter  截止时间
     * @param serialNumber  证书序列号，20字节
     * @return
     * @throws Exception
     */
    public static Certificate generateSelfSignedCertificate(X500Name subject, KeyPair keyPair, Date notBefore, Date notAfter, BigInteger serialNumber) throws Exception {
        SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
        BcX509ExtensionUtils extUtils      = new BcX509ExtensionUtils();
        ExtensionsGenerator  extGen        = new ExtensionsGenerator();
        // ca cert
        extGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(1));
        extGen.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
        extGen.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(publicKeyInfo));
        extGen.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(publicKeyInfo));
        V3TBSCertificateGenerator tbsGen = new V3TBSCertificateGenerator();
        tbsGen.setSerialNumber(new ASN1Integer(serialNumber));
        // 自签证书颁发者等于使用者
        tbsGen.setIssuer(subject);
        tbsGen.setStartDate(new Time(notBefore, Locale.CHINA));
        tbsGen.setEndDate(new Time(notAfter, Locale.CHINA));
        tbsGen.setSubject(subject);
        tbsGen.setSubjectPublicKeyInfo(publicKeyInfo);
        tbsGen.setExtensions(extGen.generate());
        // 签名算法标识等于密钥算法标识
        AlgorithmIdentifier algorithm = publicKeyInfo.getAlgorithm();
        tbsGen.setSignature(getSignAlgo(publicKeyInfo.getAlgorithm()));
        TBSCertificate tbs = tbsGen.generateTBSCertificate();
        return assembleCert(tbs, publicKeyInfo, keyPair.getPrivate());
    }
    
    /**
     * 组装证书
     * @param tbsCertificate      证书结构
     * @param issuerPublicKeyInfo 颁发者公钥
     * @param issuerPrivateKey    颁发者私钥
     * @return
     * @throws Exception
     */
    public static Certificate assembleCert(TBSCertificate tbsCertificate, SubjectPublicKeyInfo issuerPublicKeyInfo, PrivateKey issuerPrivateKey) throws Exception {
        SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(issuerPublicKeyInfo.getEncoded());
        AlgorithmIdentifier  algorithm     = publicKeyInfo.getAlgorithm();
        AlgorithmIdentifier  signAlgo      = getSignAlgo(algorithm);
        Signer               signer        = SignerFactory.produce(signAlgo.getAlgorithm());
        byte[]               signature     = signer.sign(tbsCertificate.getEncoded(), issuerPrivateKey);
        ASN1EncodableVector  vector        = new ASN1EncodableVector();
        vector.add(tbsCertificate);
        vector.add(signAlgo);
        vector.add(new DERBitString(signature));
        return Certificate.getInstance(new DERSequence(vector));
    }
    
    /**
     * 根据公钥算法标识返回对应签名算法标识
     *
     * @param asymAlgo 公钥算法标识
     * @return 签名算法标识
     */
    public static AlgorithmIdentifier getSignAlgo(AlgorithmIdentifier asymAlgo) {
        ASN1ObjectIdentifier algorithm  = asymAlgo.getAlgorithm();
        ASN1Encodable        parameters = asymAlgo.getParameters();
        if (algorithm.equals(X9ObjectIdentifiers.id_ecPublicKey) && parameters.equals(GMObjectIdentifiers.sm2p256v1)) {
            return new AlgorithmIdentifier(GMObjectIdentifiers.sm2sign_with_sm3, DERNull.INSTANCE);
        } else if (algorithm.equals(X9ObjectIdentifiers.id_ecPublicKey) && parameters.equals(SECObjectIdentifiers.secp256k1)) {
            return new AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256);
        } else if (algorithm.equals(PKCSObjectIdentifiers.rsaEncryption)) {
            return new AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption, DERNull.INSTANCE);
        } else {
            throw new IllegalArgumentException("密钥算法不支持");
        }
    }
    
}